package com.ifonly.config;

import com.google.common.base.Strings;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.ifonly.activerecord.dialect.Dialect;
import com.ifonly.common.ViewType;
import com.ifonly.reports.Reporter;
import com.ifonly.utils.ReflectUtils;
import com.ifonly.utils.ResourcesUtils;

import java.sql.Connection;
import java.util.Locale;
import java.util.Properties;

/**
 * 全局配置信息
 *  默认读取 classpath 下的 config.properties 文件进行初始化
 *
 * @author <a href="mailto:ifonlymaster@163.com">ifonly</a>
 * @version 1.0 2015-12-09 13:15
 * @since JDK 1.6
 */
public class Configuration {
    // ~~~ 配置文件默认名称 ~~~
    private static final String DEFAULT_CONFIG_FILE = "config.properties";

    // ~~~ Configuration 采用单例模式 ~~~

    private Configuration() {

    }

    private static class InstanceHolder {
        public static Configuration instance = new Configuration();
    }

    public static Configuration getInstance() {
        return InstanceHolder.instance;
    }

    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 配置key属性
    public static final String APP_NAME = "app.name";
    public static final String APP_VERSION = "app.version";
    public static final String DEV_MODE = "dev.mode";
    public static final String REPORTER = "reporter";
    public static final String LOGGER_FACTORY = "logger.factory";
    public static final String ENCODING = "encoding";
    public static final String VIEW_PATH = "view.path";
    public static final String VIEW_TYPE = "view.type";
    public static final String VIEW_EXTENSION = "view.extension";
    public static final String MAX_UPLOAD_SIZE = "maxUploadSize";
    public static final String UPLOAD_SAVE_DIR = "upload.saveDir";

    public static final String DIALECT = "dialect";
    public static final String JDBC_URL = "jdbc.url";
    public static final String JDBC_USERNAME = "jdbc.username";
    public static final String JDBC_PASSWORD = "jdbc.password";

    public static final String DRUID_STAT = "druid.stat";
    public static final String DRUID_PATH = "druid.path";

    public static final String I18N_LOCALE = "i18n.locale";
    public static final String I18N_LANG = "i18n.lang";
    public static final String I18N_COUNTRY = "i18n.country";
    public static final String FTL_TEMPLATE_UPDATE_DELAY = "ftl.templateUpdateDelay";
    public static final String ACTION_VIEW_URL = "action.viewUrl";
    public static final String SQL_MAPPER = "sql.mapper";
    public static final String TRANSACTION_ISOLATION = "transaction.isolation";

    public static final String SECURITY = "security";
    public static final String SECURITY_FILE = "security.file";
    public static final String SECURITY_REALM = "security.realm";
    public static final String SECURITY_RETRYLIMIT = "security.retryLimit";
    public static final String SECURITY_SESSION_EXPIRED = "security.session.expired";
    public static final String LOGIN_URL = "login.url";
    public static final String SUCCESS_URL = "success.url";
    public static final String UNAUTHORIZED_URL = "unauthorized.url";
    public static final String PERMISSION_VALIDATE = "permission.validate";
    public static final String FILTER_CHAIN_DEFINITIONS = "filter.chain.definitions";
    public static final String REMEMBERME_COOKIE_MAXAGE = "rememberMe.cookie.maxAge";
    public static final String CIPHER_KEY = "cipher.key";

    public static final String CACHE = "cache";
    public static final String CACHE_TYPE = "cache.type";
    public static final String CACHE_FILE = "cache.file";

    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 默认配置
    private static final String DEFAULT_APP_NAME = "ifonly";
    private static final String DEFAULT_APP_VERSION = "1.0.0";
    private static final boolean DEFAULT_DEV_MODE = false;
    private static final String DEFAULT_REPORTER = "com.ifonly.reports.SysOutReporter";
    private static final String DEFAULT_LOGGER_FACTORY = "com.ifonly.logger.nop.NOPLoggerFactory";
    private static final String DEFAULT_ENCODING = "UTF-8";
    private static final String DEFAULT_VIEW_PATH = "/WEB-INF/views";
    private static final String DEFAULT_VIEW_TYPE = "ftl";
    private static final int DEFAULT_MAX_UPLOAD_SIZE = 1048576; //1024 * 1024; //1MB
    private static final int DEFAULT_FTL_TEMPLATE_UPDATE_DELAY = 1000;
    private static final String DEFAULT_ACTION_VIEW_URL = "/as";
    private static final boolean DEFAULT_DRUID_STAT = false;
    private static final String DEFAULT_DRUID_PATH = "/druid";
    private static final String DEFAULT_SQL_MAPPER = "/mappers/.*-sql.xml";
    private static final String DEFAULT_TRANSACTION_ISOLATION = "TRANSACTION_REPEATABLE_READ";
    private static final boolean DEFAULT_SECURITY = false;
    private static final long DEFAULT_SECURITY_SESSION_EXPIRED = 10800000L;
    private static final String DEFAULT_LOGIN_URL = "/login";
    private static final String DEFAULT_SUCCESS_URL = "/main";
    private static final String DEFAULT_UNAUTHORIZED_URL = "/unauthorized";
    private static final boolean DEFAULT_PERMISSION_VALIDATE = false;
    private static final String DEFAULT_FILTER_CHAIN_DEFINITIONS = "";
    private static final int DEFAULT_REMEMBERME_COOKIE_MAXAGE = 2592000;
    private static final String DEFAULT_CIPHER_KEY = "4AvVhmFLUs0KTA3Kprsdag==";
    private static final boolean DEFAULT_CACHE = false;
    private static final String DEFAULT_CACHE_TYPE = "ehcache";


    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 属性
    private String configFile;
    private Properties properties;

    private Reporter reporter;

    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 初始化

    /**
     * 初始化 采用 默认配置文件设置
     * @return Configuration 对象
     */
    public Configuration init() {
        return init(DEFAULT_CONFIG_FILE);
    }

    /**
     * 初始化 Configuration
     *
     * @param configFile 指定的配置文件
     * @return Configuration 对象
     */
    public Configuration init(String configFile) {
        this.configFile = configFile;

        Properties props = new Properties();
        ResourcesUtils.loadFileInProperties(configFile, props);
        return init(props);
    }

    /**
     * 初始化 Configuration
     *
     * @param properties Properties
     * @return Configuration 对象
     */
    public Configuration init(Properties properties) {
        this.properties = properties;
        return this;
    }


    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ getters

    public String getConfigFile() {
        return configFile;
    }

    public Properties getProperties() {
        return properties;
    }

    public String getProperty(String key) {
        return getProperty(key, null);
    }

    public String getProperty(String key, String defaultValue) {
        return properties.getProperty(key, defaultValue);
    }

    public int getInt(String key) {
        return getInt(key, 0);
    }

    public int getInt(String key, int defaultValue) {
        String value = getProperty(key);
        if (null == value) {
            return defaultValue;
        }
        return Ints.tryParse(value);
    }

    public long getLong(String key) {
        return getLong(key, 0L);
    }

    public long getLong(String key, long defaultValue) {
        String value = getProperty(key);
        if (null == value) {
            return defaultValue;
        }
        return Longs.tryParse(value);
    }

    public boolean getBoolean(String key) {
        return getBoolean(key, false);
    }

    public boolean getBoolean(String key, boolean defaultValue) {
        String value = getProperty(key);
        if (null == value) {
            return defaultValue;
        }
        return Boolean.parseBoolean(value);
    }

    // ~~~~~~~ get config info ~~~~~~~

    public String getAppName() {
        return getProperty(APP_NAME, DEFAULT_APP_NAME);
    }

    public String getAppVersion() {
        return getProperty(APP_VERSION, DEFAULT_APP_VERSION);
    }

    public boolean isDevMode() {
        return getBoolean(DEV_MODE, DEFAULT_DEV_MODE);
    }

    public synchronized Reporter getReporter() {
        if (reporter == null) {
            String property = getProperty(REPORTER, DEFAULT_REPORTER);
            Object temp = ReflectUtils.newInstance(property);
            if (temp instanceof Reporter) {
                reporter = (Reporter) temp;
            } else {
                throw new RuntimeException("the config of reporter not a subclass of Reporter");
            }
        }
        return reporter;
    }

    public String getLoggerFactory() {
        return getProperty(LOGGER_FACTORY, DEFAULT_LOGGER_FACTORY);
    }

    public String getEncoding() {
        return getProperty(ENCODING, DEFAULT_ENCODING);
    }

    public String getViewPath() {
        String viewPath = getProperty(VIEW_PATH, DEFAULT_VIEW_PATH);
        if (!viewPath.endsWith("/")) {
            return viewPath + "/";
        }
        return viewPath;
    }

    public String getViewType() {
        return getProperty(VIEW_TYPE, DEFAULT_VIEW_TYPE);
    }

    public String getViewExtension() {
        String ext = getProperty(VIEW_EXTENSION);
        if (ext == null) {
            ViewType viewType = ViewType.get(getViewType());
            if (viewType != null) {
                ext = viewType.extension;
            }
        }
        return ext;
    }

    public int getMaxUploadSize() {
        return getInt(MAX_UPLOAD_SIZE, DEFAULT_MAX_UPLOAD_SIZE);
    }

    public String getUploadSaveDir() {
        return getProperty(UPLOAD_SAVE_DIR);
    }

    public Locale getLocale() {
        String i18nLocale = getProperty(I18N_LOCALE, null);
        if (!Strings.isNullOrEmpty(i18nLocale)) {
            String[] array = i18nLocale.split("_");
            if (array.length == 1)
                return new Locale(array[0]);
            return new Locale(array[0], array[1]);
        }

        String i18nLang = getProperty(I18N_LANG, null);
        if (Strings.isNullOrEmpty(i18nLang)) {
            return Locale.getDefault();
        }
        String i18nCountry = getProperty(I18N_COUNTRY, null);
        if (i18nCountry == null) {
            return new Locale(i18nLang);
        }
        return new Locale(i18nLang, i18nCountry);
    }

    public int getFtlTemplateUpdateDelay() {
        return getInt(FTL_TEMPLATE_UPDATE_DELAY, DEFAULT_FTL_TEMPLATE_UPDATE_DELAY);
    }

    public String getActionViewUrl() {
        return getProperty(ACTION_VIEW_URL, DEFAULT_ACTION_VIEW_URL);
    }

    public String getDialect() {
        String dialect = getProperty(DIALECT);
        if (Strings.isNullOrEmpty(dialect)) {
            dialect = Dialect.parseFromUrl(getJdbcUrl());
        }
        if (Strings.isNullOrEmpty(dialect)) {
            throw new UnsupportedOperationException("Unsupported dialect");
        }
        return dialect;
    }

    public String getJdbcUsername() {
        String jdbcUsername = getProperty(JDBC_USERNAME);
        if (Strings.isNullOrEmpty(jdbcUsername)) {
            throw new RuntimeException("please config jdbc.username");
        }
        return jdbcUsername;
    }

    public String getJdbcPassword() {
        String jdbcPassword = getProperty(JDBC_PASSWORD);
        if (Strings.isNullOrEmpty(jdbcPassword)) {
            throw new RuntimeException("please config jdbc.password");
        }
        return jdbcPassword;
    }

    public String getJdbcUrl() {
        String jdbcUrl = getProperty(JDBC_URL);
        if (Strings.isNullOrEmpty(jdbcUrl)) {
            throw new RuntimeException("please config jdbc.url");
        }
        return jdbcUrl;
    }

    public String getSqlMapper() {
        return getProperty(SQL_MAPPER, DEFAULT_SQL_MAPPER);
    }

    public boolean isDruidStat() {
        return getBoolean(DRUID_STAT, DEFAULT_DRUID_STAT);
    }

    public String getDruidPath() {
        return getProperty(DRUID_PATH, DEFAULT_DRUID_PATH);
    }

    public int getTransactionIsolation() {
        String transactionIsolation = getProperty(TRANSACTION_ISOLATION, DEFAULT_TRANSACTION_ISOLATION);
        if ("TRANSACTION_NONE".equals(transactionIsolation)) {
            return Connection.TRANSACTION_NONE;
        } else if ("TRANSACTION_READ_UNCOMMITTED".equals(transactionIsolation)) {
            return Connection.TRANSACTION_READ_UNCOMMITTED;
        } else if ("TRANSACTION_READ_COMMITTED".equals(transactionIsolation)) {
            return Connection.TRANSACTION_READ_COMMITTED;
        } else if ("TRANSACTION_REPEATABLE_READ".equals(transactionIsolation)) {
            return Connection.TRANSACTION_REPEATABLE_READ;
        } else if ("TRANSACTION_SERIALIZABLE".equals(transactionIsolation)) {
            return Connection.TRANSACTION_SERIALIZABLE;
        }
        throw new RuntimeException("transaction.isolation config error, " +
                "Must be one of: Connection.TRANSACTION_NONE, " +
                "Connection.TRANSACTION_READ_UNCOMMITTED, " +
                "Connection.TRANSACTION_READ_COMMITTED, " +
                "Connection.TRANSACTION_REPEATABLE_READ, " +
                "Connection.TRANSACTION_SERIALIZABLE");
    }

    public boolean isSecurity() {
        return getBoolean(SECURITY, DEFAULT_SECURITY);
    }

    public String getSecurityFile() {
        return getProperty(SECURITY_FILE);
    }

    public String getSecurityRealm() {
        return getProperty(SECURITY_REALM);
    }

    public boolean isSecurityRetryLimit() {
        return getBoolean(SECURITY_RETRYLIMIT, isSecurity());
    }

    public long getSecuritySessionExpired() {
        return getLong(SECURITY_SESSION_EXPIRED, DEFAULT_SECURITY_SESSION_EXPIRED);
    }

    public String getLoginUrl() {
        return getProperty(LOGIN_URL, DEFAULT_LOGIN_URL);
    }

    public String getSuccessUrl() {
        return getProperty(SUCCESS_URL, DEFAULT_SUCCESS_URL);
    }

    public String getUnauthorizedUrl() {
        return getProperty(UNAUTHORIZED_URL, DEFAULT_UNAUTHORIZED_URL);
    }

    public boolean isPermissionValidate() {
        return getBoolean(PERMISSION_VALIDATE, DEFAULT_PERMISSION_VALIDATE);
    }

    public String getFilterChainDefinitions() {
        return getProperty(FILTER_CHAIN_DEFINITIONS, DEFAULT_FILTER_CHAIN_DEFINITIONS);
    }

    public int getRememberMeCookieMaxAge() {
        return getInt(REMEMBERME_COOKIE_MAXAGE, DEFAULT_REMEMBERME_COOKIE_MAXAGE);
    }

    public String getCipherKey() {
        return getProperty(CIPHER_KEY, DEFAULT_CIPHER_KEY);
    }

    public boolean isCache() {
        return getBoolean(CACHE, DEFAULT_CACHE);
    }

    public String getCacheType() {
        return getProperty(CACHE_TYPE, DEFAULT_CACHE_TYPE);
    }

    public String getCacheFile() {
        return getProperty(CACHE_FILE);
    }

}
